 /**
 * rcat - An cat implemented by Renê =:)
 *
 * Author: Renê de Souza Pinto < rene@grad.icmc.usp.br >
 * Date: Mar, 2009
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define VERSION         "1.0"
#define BUG_REPORTS "rene@grad.icmc.usp.br"

#define BUFFER_SIZE 4096
#define OPTS_SIZE       7
#define NONB            0
#define NUM                     1
#define SQU                     2
#define SHOWT           3
#define SHOWNP          4
#define SHOWENDS    5
#define NONV            6


/* prototypes */
unsigned int read_and_print(int fdin,  int fdout, char opts[], unsigned int lines);
void cprint(const char *format, ...);
void show_help(char **argv);


/* main */
int main(int argc, char **argv)
{
        int fdin, fdout;
        unsigned int i, lines;
        char opts[OPTS_SIZE];
        static struct option longOpts[] = {
                {"show-all",         no_argument, NULL, 'A'},
                {"number-nonblank",  no_argument, NULL, 'b'},
                {"show-ends",        no_argument, NULL, 'E'},
                {"number",           no_argument, NULL, 'n'},
                {"squeeze-blank",    no_argument, NULL, 's'},
                {"show-tabs",        no_argument, NULL, 'T'},
                {"show-nonprinting", no_argument, NULL, 'v'},
                {"help",             no_argument, NULL, 'h'},
                {"version",          no_argument, NULL, 'i'},
                {NULL, no_argument, NULL, 0}
        };
        const char *optstring = "AbEnsTvethi-";
        int opt, optli;

        /* Initialize variables */
        fdin  = STDIN_FILENO;
        fdout = STDOUT_FILENO;
        for(i=0; i<OPTS_SIZE; i++)
                opts[i] = 0;

        /* Treat command line */
        opt = getopt_long(argc, argv, optstring, longOpts, &optli);
        while(opt != -1) {
                switch(opt) {
                        case 'A':
                        case 'E':
                                opts[SHOWENDS] = 1;
                                if(opt != 'A' && opt != 'e')
                                        break;

                        case 't':
                        case 'v':
                                opts[NONV] = 1;
                                if(opt != 'A' && opt != 'e' && opt != 't')
                                        break;

                        case 'T':
                                opts[SHOWT] = 1;
                                break;

                        case 'b':
                                opts[NONB] = 1;
                                break;

                        case 'n':
                                opts[NUM] = 1;
                                break;

                        case 's':
                                opts[SQU] = 1;
                                break;

                        case 'h':
                                show_help(argv);
                                break;

                        case 'i':
                                cprint("\nVersion: %s", VERSION);
                                cprint("\nBUG reports to: %s\n\n", BUG_REPORTS);
                                break;
                }
                opt = getopt_long(argc, argv, optstring, longOpts, &optli);
        }

        /* Read files and write to output */
        if(argc == 1) {
                read_and_print(fdin, fdout, opts, 0);
        }

        lines = 0;
        for(i=1; i < argc; i++) {

                if(strlen(argv[i]) > 1 && argv[i][0] == '-')
                        continue;

                /* Open file for read */
                if(strlen(argv[i]) == 1 && argv[i][0] == '-') {
                        fdin = STDIN_FILENO;
                } else {
                        if((fdin = open(argv[i], O_RDONLY)) < 0) {
                                cprint("Could not read file %s\n", argv[i]);
                        }
                }

                lines = read_and_print(fdin, fdout, opts, lines);

                /* If it's not the standard input, close it */
                if(fdin != STDIN_FILENO)
                        close(fdin);
        }

        return(EXIT_SUCCESS);
}


/**
 * read_and_print
 * Read from fdin and write to fdout
 */
unsigned int read_and_print(int fdin,  int fdout, char opts[], unsigned int lines)
{
        unsigned int line, i;
        char buffer[BUFFER_SIZE];
        ssize_t count;
        char start, firstb;

        if(lines == 0) {
                line = 1;
        } else {
                line = lines;
        }

        start  = 1;
        firstb = 0;
        while((count=read(fdin, buffer, BUFFER_SIZE)) > 0) {

                for(i=0; i<count; i++) {

                        /* Start-of-line */
                        if(start) {
                                if(opts[NUM] || (opts[NONB] && buffer[i] != '\n')) {
                                        cprint("%d ", line);
                                        line++;
                                }

                                if(opts[SQU] && buffer[i] == '\n') {
                                        if(!firstb) {
                                                firstb = 1;
                                        } else {
                                                start = 1;
                                                continue;
                                        }
                                } else {
                                        firstb = 0;
                                }

                                start  = 0;
                        }

                        /* End-of-line */
                        if(buffer[i] == '\n') {
                                if(opts[SHOWENDS]) {
                                        cprint("$\n");
                                } else {
                                        cprint("\n");
                                }
                                start = 1;
                        } else {
                                /* Middle of line */
                                if(opts[SHOWT] && buffer[i] == '\t') {
                                                cprint("^I");
                                } else {
                                        cprint("%c", buffer[i]);
                                }
                        }
                }


        }
        return(line);
}


/**
 * cprint
 * Print a message to standard output
 */
void cprint(const char *format, ...)
{
        char buffer[BUFFER_SIZE];
        int ssize;
        va_list ap;
        va_start(ap, format);

        ssize = vsnprintf(buffer, BUFFER_SIZE, format, ap);
        write(STDOUT_FILENO, buffer, ssize);

        va_end(ap);
}


/**
 * show_help
 * Show rcat help
 */
void show_help(char **argv)
{
        cprint("\nUsage: %s [OPTION] [FILE]...", argv[0]);
        cprint("\nConcatenate FILE(s), or standard input, to standard output.\n");

        cprint("\n  -A, --show-all           equivalent to -vET");
        cprint("\n  -b, --number-nonblank    number nonempty output lines");
        cprint("\n  -e                       equivalent to -vE");
        cprint("\n  -E, --show-ends          display $ at end of each line");
        cprint("\n  -n, --number             number all output lines");
        cprint("\n  -s, --squeeze-blank      suppress repeated empty output lines");
        cprint("\n  -t                       equivalent to -vT");
        cprint("\n  -T, --show-tabs          display TAB characters as ^I");
        /*cprint("\n  -v, --show-nonprinting   use ^ and M- notation, except for LFD and TAB");*/
        cprint("\n      --help     display this help and exit");
        cprint("\n      --version  output version information and exit\n");

        cprint("\nWith no FILE, or when FILE is -, read standard input.");
        cprint("\n");
}


